<?php
namespace Util;

class ModelUtils
{

    /**
     * 一对多关联查询
     * @param  array   $records           表记录信息，注：必须要包括关联字段，会改变该数组结构
     * @param  string  $relationModelName 关联表名
     * @param  string  $refKey            关联表的外键名
     * @param  string  $name              将信息保存到$reocrds元素的哪个字段
     * @param  string  $primaryKey        关联字段名，不一定是主键，与关联表的外键名对应
     * @param  string  $fields            指定字段，默认为所有,必须要包括关联字段
     * @return null
     */
    public static function hasMany(array &$records, $options)
    {
        $relationModelName = $options['relation'];
        $foreignKey = $options['foreignKey'];
        $name = $options['name'] ? : strtolower($relationModelName) . 's';
        $refKey = $options['refKey'] ? : 'id';
        $scope = $options['scope'];
        $fileds = $options['fields'] ?: '*';
        $order = $options['order'];

        if (empty($records)) return;
        if (array_key_exists($refKey, $records)) {
            $condition[$foreignKey] = $records[$refKey];
            $relationModel = D($relationModelName);
            $relationModel->field($fileds);
            if ($scope) $relationModel->scope($scope);
            if ($order) $relationModel->order($order);
            $relationRecords = $relationModel->where($condition)->select();
            $records[$name] = $relationRecords;
        } else {
            $foreignIds = array_column($records, $refKey);
            $condition[$foreignKey] = ['in', $foreignIds];
            $relationModel = D($relationModelName);
            $relationModel->field($fileds);
            if ($scope) $relationModel->scope($scope);
            if ($order) $relationModel->order($order);
            $relationRecords = $relationModel->where($condition)->select();
            $relationRecordsGroup = self::groupingArray($relationRecords, $foreignKey);
            foreach ($records as &$record) {
                $record[$name] = $relationRecordsGroup[$record[$refKey]] ?: [];
            }
        }
    }

    /**
     * 一对一关联查询
     * @author ruben
     * @version 2.0
     * @param  array  $records           表记录信息，注：必须要包括关联字段，会改变该数组结构
     * @param  array options
     * @param  string $foreignKey        外键名
     * @param  string $name              将信息保存到$reocrds元素的哪个字段
     * @param  string $refKey            主表ID
     * @param  string $fields            指定字段，默认为所有,必须要包括关联字段
     * @return null
     */
    public static function belongsTo(array &$records, $options = [])
    {
        $relationModelName = $options['relation'];
        $foreignKey = $options['foreignKey'] ? : strtolower($relationModelName) . '_id';
        $name = $options['name'] ? : strtolower($relationModelName);
        $refKey = $options['refKey'] ? : 'id';
        $scope = $options['scope'];
        $fileds = $options['fields'] ?: '*';
        $order = $options['order'] ? : null;
        $condition = $options['where'];
        if (empty($records)) return;
        if (array_key_exists($foreignKey, $records)) {
            $relationModel = D($relationModelName);
            $relationModel->field($fileds);
            if ($scope) $relationModel->scope($scope);
            $relationRecord = $relationModel->find($records[$foreignKey]);
            $records[$name] = $relationRecord;
        } else {
            $foreignIds = array_column($records, $foreignKey);
            $condition[$refKey] = ['in', array_unique($foreignIds)];
            $relationModel = D($relationModelName);
            $relationModel->field($fileds);
            if ($scope) $relationModel->scope($scope);
            if ($order) $relationModel->order($order);
            $relationRecords = $relationModel->where($condition)->select();
            $relationRecordsDict = array_column($relationRecords, null, $refKey);
            foreach ($records as &$record) {
                $record[$name] = $relationRecordsDict[$record[$foreignKey]];
            }
        }
    }

    /**
     * 一对多关联查询，用于关联包含多个外键场景
     * @param  array   $records           表记录信息，注：必须要包括关联字段，会改变该数组结构
     * @param  string  $relationModelName 关联表名
     * @param  string  $foreignKey        外键名
     * @param  string  $name              将信息保存到$reocrds元素的哪个字段
     * @param  string  $refKey            从表关联字段名
     * @param  string  $fields            要查询的字段
     * @param  string  $splitChar         分隔符，默认为","
     */
    public static function hasManyInField(array &$records, $relationModelName, $foreignKey, $name, $refKey = 'id', $fields = '*', $splitChar = ',')
    {
        if (empty($records)) return;
        if (isset($records[$foreignKey])) {
            $ids = explode($splitChar, $records[$foreignKey]);
            $condition[$refKey] = ['in', $ids];
            $relationRecords = D($relationModelName)->field($fields)->where($condition)->select();
            $records[$name] = $relationRecords;
        } else {
            $ids = array_column($records, $foreignKey);
            $ids = implode($splitChar, $ids);
            $ids = explode($splitChar, $ids);
            $ids = array_unique($ids);
            $condition[$refKey] = ['in', $ids];
            $relationRecords = D($relationModelName)->field($fields)->where($condition)->select();
            $relationRecordsDict = array_column($relationRecords, null, $refKey);
            foreach ($records as &$record) {
                $record[$name] = self::getArrayBatch($relationRecordsDict, explode(',', $record[$foreignKey]));
            }

        }
    }

    /**
     * 一对一关联查询
     * @param  array   $records           表记录信息，注：必须要包括关联字段，会改变该数组结构
     * @param  string  $relationModelName 关联表名
     * @param  string  $refKey            从表关联字段
     * @param  string  $name              将信息保存到$reocrds元素的哪个字段
     * @param  string  $primaryKey        主表关联字段名
     * @param  string  $fields            要查询的字段
     */
    public static function hasOne(array &$records, $options = [])
    {
        $relationModelName = $options['relation'];
        $foreignKey = $options['foreignKey'];
        $name = $options['name'] ? : strtolower($relationModelName);
        $refKey = $options['refKey'] ? : 'id';
        $scope = $options['scope'];
        $fileds = $options['fields'] ?: '*';
        
        if (empty($records)) return;
        if (array_key_exists($refKey, $records)) {
            $condition[$foreignKey] = $records[$refKey];
            $relationModel = D($relationModelName);
            $relationModel->field($fileds);
            if ($scope) $relationModel->scope($scope);
            $relationRecord = $relationModel->where($condition)->find();
            $records[$name] = $relationRecord;
        } else {
            $refIds = array_column($records, $refKey);
            $condition[$foreignKey] = ['in', $refIds];
            $relationModel = D($relationModelName);
            $relationModel->field($fileds);
            if ($scope) $relationModel->scope($scope);
            $relationRecords = $relationModel->where($condition)->select();
            $relationRecordsDict = array_column($relationRecords, null, $refKey);
            foreach ($records as &$record) {
                $record[$name] = $relationRecordsDict[$record[$foreignKey]];
            }
        }
    }

    /* ['Order.id', 'OrderAllocation.order_id', 'OrderItem.id', 'OrderAllocation.item_id']*/
    public static function manyToMany(array &$records, $relation, $name, $fields = '*')
    {
        if (empty($records)) return;
        $relationArray = explode('.', implode('.', $relation));
        $primaryModelName = $relationArray[0];
        $primaryModelColumn = $relationArray[1];
        $middleModelName = $relationArray[2];
        $middleModelPrimaryColumn = $relationArray[3];
        $refModelName = $relationArray[4];
        $refModelColumn = $relationArray[5];
        $middleModelRefColumn = $relationArray[7];

        if (isset($records[$primaryModelColumn])) {
            $condition = [];
            $condition[$middleModelPrimaryColumn] = $records[$primaryModelColumn];
            $middleRecords = D($middleModelName)->where($condition)->select();
            $middleGroup = self::groupingArray($middleRecords, $middleModelPrimaryColumn);
            if (empty($middleRecords)) return;
            $condition2 = [];
            $condition2[$refModelColumn] = ['in', array_column($middleRecords, $middleModelRefColumn)];
            $relationRecords = D($refModelName)->field($fields)->where($condition2)->select();
            $relationDict = array_column($relationRecords, null, $refModelColumn);
            $records[$name] = self::getArrayBatch($relationDict, array_column($middleGroup[$records[$primaryModelColumn]], $middleModelRefColumn));
        } else {
            $ids = array_column($records, $primaryModelColumn);
            if (empty($ids)) return;
            $condition = [];
            $condition[$middleModelPrimaryColumn] = ['in', $ids];
            $middleRecords = D($middleModelName)->where($condition)->select();
            $middleGroup = self::groupingArray($middleRecords, $middleModelPrimaryColumn);
            if (empty($middleRecords)) return;
            $condition2 = [];
            $condition2[$refModelColumn] = ['in', array_column($middleRecords, $middleModelRefColumn)];
            $relationRecords = D($refModelName)->field($fields)->where($condition2)->select();
            $relationDict = array_column($relationRecords, null, $refModelColumn);
            foreach ($records as &$record) {
                $record[$name] = self::getArrayBatch($relationDict, array_column($middleGroup[$record[$primaryModelColumn]], $middleModelRefColumn));
            }
        }
    }

    private static function getArrayBatch(array $dict, $keys)
    {
        $array = [];
        foreach ($keys as $key) {
            if (isset($dict[$key])) {
                $array[] = $dict[$key];
            }
        }
        return $array;
    }

    private static function groupingArray(array $list, $groupName)
    {
        $groups = array_column($list, $groupName);
        $groupDict = array_fill_keys($groups, []);
        foreach ($list as $item) {
            $groupDict[$item[$groupName]][] = $item;
        }
        return $groupDict;
    }


}
